﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using OverStore.Runtime.PersistenceServiceProvider;
using System.Diagnostics;
using OverStore.Runtime.ObjectRepository;
using OverStore.Scoping.Manager;

namespace OverStore.Runtime.CollectionPersisting
{
    /// <summary>
    /// Supports persisting of classes implements <see cref="ICollection{TInstance}"/> interface.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    public class GenericCollectionPersister<TInstance> : ICollectionPersister<TInstance>
        where TInstance : class
    {
        private readonly IPersistenceServiceProvider _Provider;
        private readonly OperationScopeManager _OperationScopeManager;
        private readonly DumpCollectionPersisterChangesTracker<TInstance> _ChangesTracker = new DumpCollectionPersisterChangesTracker<TInstance>();

        private readonly IDictionary<IEnumerable<TInstance>, int> _SavedInstances = new Dictionary<IEnumerable<TInstance>, int>(ReferenceEqualityComparer<IEnumerable<TInstance>>.Instance);
        private readonly IDictionary<IEnumerable<TInstance>, int> _DirtyInstances = new Dictionary<IEnumerable<TInstance>, int>(ReferenceEqualityComparer<IEnumerable<TInstance>>.Instance);


        /// <summary>
        /// Initializes a new instance of the <see cref="GenericCollectionPersister&lt;TInstance&gt;"/> class.
        /// </summary>
        /// <param name="operationScopeManager">The operation scope manager.</param>
        /// <param name="provider">The provider.</param>
        public GenericCollectionPersister(OperationScopeManager operationScopeManager, IPersistenceServiceProvider provider)
        {
            if (Object.ReferenceEquals(provider, null))
                throw new ArgumentNullException("provider");
            if (Object.ReferenceEquals(operationScopeManager, null))
                throw new ArgumentNullException("operationScopeManager");
            _OperationScopeManager = operationScopeManager;
            _Provider = provider;
        }

        /// <summary>
        /// Returns <c>true</c> if implementation can persists collection of specified type, <c>false</c> otherwise.
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        public bool SupportCollectionPersisting(IEnumerable<TInstance> collection)
        {
            if (Object.ReferenceEquals(collection, null))
                throw new ArgumentNullException("collection");
            return collection as ICollection<TInstance> != null;
        }

        /// <summary>
        /// Clears the collection and fills it again using items extracted from parameterless reader.
        /// </summary>
        /// <param name="collection"></param>
        public void Reload(IEnumerable<TInstance> collection)
        {
            if (Object.ReferenceEquals(collection, null))
                throw new ArgumentNullException("collection");
            var c = (ICollection<TInstance>)collection;

            c.Clear();

            if (_ChangesTracker != null)
                _ChangesTracker.RejectChanges(collection);

            foreach (var instance in _Provider.ExecuteObjectReader<TInstance>())
            {
                _ChangesTracker.RegisterItem(collection, instance);
                c.Add(instance);
            }
        }

        /// <summary>
        /// Reloads the specified collection.
        /// </summary>
        /// <typeparam name="TParameter">The type of the parameter.</typeparam>
        /// <param name="collection">The collection.</param>
        /// <param name="parameter">The parameter.</param>
        public void Reload<TParameter>(IEnumerable<TInstance> collection, TParameter parameter) where TParameter : class
        {
            if (Object.ReferenceEquals(collection, null))
                throw new ArgumentNullException("collection");
            var c = (ICollection<TInstance>)collection;

            if (_ChangesTracker != null)
                _ChangesTracker.RejectChanges(collection);

            c.Clear();

            foreach (var instance in _Provider.ExecuteObjectReader<TInstance, TParameter>(parameter))
            {
                _ChangesTracker.RegisterItem(collection, instance);
                c.Add(instance);
            }
        }

        /// <summary>
        /// Saves the each item in specified collection, including items were deleted from collection, if they can be determined.
        /// </summary>
        /// <param name="collection"></param>
        public void Save(IEnumerable<TInstance> collection)
        {
            Debug.Assert(collection != null, "collection != null");

            if (_SavedInstances.ContainsKey(collection))
                return;
            _SavedInstances[collection] = 1;

            // Because storage data may contains references to items marked as deleted but 
            // objects in session must not contains such references, save existing items before deleted.
            foreach (var item in collection)
                _Provider.Save<TInstance>(item);

            foreach (var deleted in _ChangesTracker.GetDeletedItems(collection))
            {
                _Provider.MarkAsDeleted<TInstance>(deleted);
                _Provider.Save<TInstance>(deleted);
            }
        }

        private void OnScopeCompleted(object sender, OperationScopeActionEventArgs e)
        {
            if (e.Action == OperationScopeAction.PostCompleting)
            {
                foreach (var collection in _SavedInstances.Keys)
                {
                    if (_ChangesTracker != null)
                    {
                        _ChangesTracker.RejectChanges(collection);
                        foreach (var item in collection)
                            _ChangesTracker.RegisterItem(collection, item);
                    }
                }
                _SavedInstances.Clear();
                _DirtyInstances.Clear();
            }
        }

        /// <summary>
        /// Adds the content of specified collection to session.
        /// </summary>
        /// <param name="collection">The collection.</param>
        public void Add(IEnumerable<TInstance> collection)
        {
            if (Object.ReferenceEquals(collection, null))
                throw new ArgumentNullException("collection");
            foreach (var item in collection)
                _Provider.Add(item);
        }

        /// <summary>
        /// Marks every item in collection as deleted.
        /// </summary>
        /// <param name="collection"></param>
        public void MarkAsDeleted(IEnumerable<TInstance> collection)
        {
            foreach (var item in collection)
                _Provider.MarkAsDeleted(item);
        }

        /// <summary>
        /// Attaches each instance of specified collection, including deleted items if can be determined.
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        public IEnumerable<TInstance> Attach(IEnumerable<TInstance> collection)
        {
            if (Object.ReferenceEquals(collection, null))
                throw new ArgumentNullException("collection");

            var c = (ICollection<TInstance>)collection;
            // Prepare iterator that attaches every item in source collection and return attached item.
            var items = c.Select(i => _Provider.Attach<TInstance>(i))
                // Call ToList method for access collection content before call ContentManager.FillCollection method,
                // because this method clears collection before filling.
                                       .ToList();

            // Fill collection with attached items.
            c.Clear();
            foreach (var i in items)
                c.Add(i);
            if (_ChangesTracker != null)
                _ChangesTracker.Attach(collection, _ => Enumerable.Empty<TInstance>());
            return collection;
        }
    }
}
